#!/usr/bin/env python3
"""
Retry Manager for GSI/EnKF Automated Code Commentary System

This module handles retry logic and error correction for diff patches.

Author: GSI Development Team
Date: 2025-01-XX
"""

import logging
import re
import time
from pathlib import Path
from typing import List, Optional
from collections import Counter

from ..core.models import PatchError, DiffAttempt

logger = logging.getLogger(__name__)


class DiffRetryManager:
    """Manages retry logic and error correction for diff patches"""
    
    def __init__(self, max_attempts: int = 1, base_backoff: float = 1.0, max_backoff: float = 30.0):
        self.max_attempts = max_attempts
        self.base_backoff = base_backoff  # Base delay for exponential backoff
        self.max_backoff = max_backoff    # Maximum delay between attempts
        
        # Error type priority for recovery attempts
        self.error_priority = {
            "CONTEXT_ERROR": 1,     # Most recoverable
            "LINE_MISMATCH": 2,     
            "SYNTAX_ERROR": 3,      
            "GENERATION_FAILED": 4,
            "SYNTAX_VALIDATION": 5,
            "PROCESSING_ERROR": 6,  # Least recoverable
            "UNKNOWN_ERROR": 7
        }
        
        # Recovery strategies for different error types
        self.recovery_strategies = {
            "CONTEXT_ERROR": "CONTEXT_REFINEMENT",
            "LINE_MISMATCH": "LINE_ADJUSTMENT", 
            "SYNTAX_ERROR": "FORMAT_CORRECTION",
            "GENERATION_FAILED": "PROMPT_SIMPLIFICATION",
            "SYNTAX_VALIDATION": "ROLLBACK_CORRECTION",
            "PROCESSING_ERROR": "ENVIRONMENT_RESET",
            "UNKNOWN_ERROR": "COMPREHENSIVE_ANALYSIS"
        }
        
    def should_retry(self, attempt_count: int, error: Optional[PatchError] = None) -> bool:
        """Enhanced retry decision based on attempt count and error type"""
        if attempt_count >= self.max_attempts:
            return False
            
        # Consider error severity for retry decision
        if error and error.severity == "LOW":
            # Low severity errors can have more attempts
            return attempt_count < (self.max_attempts + 1)
        elif error and error.severity == "HIGH":
            # High severity errors need fewer attempts to avoid wasting time
            return attempt_count < max(1, self.max_attempts - 1)
        
        return True
    
    def calculate_backoff_time(self, attempt_number: int, error_type: str = "UNKNOWN_ERROR") -> float:
        """Calculate exponential backoff time with jitter and error-type considerations"""
        # Base exponential backoff: 2^(attempt-1) * base_backoff
        base_delay = (2 ** (attempt_number - 1)) * self.base_backoff
        
        # Adjust delay based on error type priority
        error_priority = self.error_priority.get(error_type, 5)
        priority_multiplier = 0.5 + (error_priority * 0.1)  # 0.6 to 1.2 multiplier
        
        adjusted_delay = base_delay * priority_multiplier
        
        # Cap at maximum backoff time
        capped_delay = min(adjusted_delay, self.max_backoff)
        
        # Add jitter (±20% random variation) to avoid thundering herd
        import random
        jitter_factor = 0.8 + (random.random() * 0.4)  # 0.8 to 1.2
        final_delay = capped_delay * jitter_factor
        
        return final_delay
        
    def analyze_patch_error(self, error_message: str, diff_content: str) -> PatchError:
        """Enhanced patch error analysis with detailed categorization"""
        error_message_lower = error_message.lower()
        
        # Context mismatch errors (most recoverable)
        if any(keyword in error_message_lower for keyword in ["line does not match", "context", "hunk failed"]):
            line_match = re.search(r'line (\d+)', error_message)
            line_number = int(line_match.group(1)) if line_match else None
            
            severity = "MEDIUM"
            if "whitespace" in error_message_lower:
                severity = "LOW"
            elif "does not exist" in error_message_lower:
                severity = "HIGH"
            
            return PatchError(
                error_type="CONTEXT_ERROR",
                error_message=error_message,
                line_number=line_number,
                context="Context lines don't match original file",
                suggested_fix="Check line numbers and context lines accuracy",
                severity=severity,
                recovery_strategy=self.recovery_strategies["CONTEXT_ERROR"]
            )
            
        # Format and syntax errors
        elif any(keyword in error_message_lower for keyword in ["malformed patch", "corrupt patch", "invalid format"]):
            return PatchError(
                error_type="SYNTAX_ERROR", 
                error_message=error_message,
                context="Invalid diff format",
                suggested_fix="Fix diff headers and hunk format",
                severity="MEDIUM",
                recovery_strategy=self.recovery_strategies["SYNTAX_ERROR"]
            )
            
        # Line conflicts
        elif any(keyword in error_message_lower for keyword in ["already exists", "patch would not apply"]):
            return PatchError(
                error_type="LINE_MISMATCH",
                error_message=error_message,
                context="Line already exists or conflicting changes",
                suggested_fix="Adjust line numbers or context",
                severity="MEDIUM",
                recovery_strategy=self.recovery_strategies["LINE_MISMATCH"]
            )
            
        # Unknown errors
        else:
            return PatchError(
                error_type="UNKNOWN_ERROR",
                error_message=error_message,
                context="Unrecognized patch error",
                suggested_fix="Review entire diff format",
                severity="HIGH",
                recovery_strategy=self.recovery_strategies["UNKNOWN_ERROR"]
            )
    
    def generate_correction_prompt(self, original_diff: str, error: PatchError, 
                                 original_content: str, file_path: str) -> str:
        """Generate specific correction prompt based on error type"""
        
        base_prompt = f"""The previous diff patch failed to apply with this error:
ERROR: {error.error_message}
ERROR TYPE: {error.error_type}

Original file content:
```fortran
{original_content}
```

Failed diff patch:
```diff
{original_diff}
```

CORRECTION NEEDED: {error.suggested_fix}

Generate a corrected unified diff patch that fixes this specific error:"""

        if error.error_type == "CONTEXT_ERROR":
            return base_prompt + f"""
- Check that context lines (prefixed with space) exactly match the original file
- Verify line numbers in @@ headers are accurate
- Include sufficient context before and after changes
- Line {error.line_number} area needs attention""" if error.line_number else ""
            
        elif error.error_type == "SYNTAX_ERROR":
            return base_prompt + """
- Ensure diff starts with '--- a/filename' and '+++ b/filename'  
- Check @@ hunk headers use correct format: @@ -old_start,old_count +new_start,new_count @@
- Verify all lines are properly prefixed (space, +, or -)
- No missing or extra characters in diff format"""
            
        elif error.error_type == "LINE_MISMATCH":
            return base_prompt + """
- Check if the line already has comments or differs from expected
- Adjust line numbers if file has been modified
- Ensure added lines don't conflict with existing content"""
            
        else:
            return base_prompt + """
- Review entire diff format for any issues
- Ensure file path and line numbers are correct
- Check for any special characters or encoding issues"""
    
    def log_final_failure(self, file_path: str, attempts: List[DiffAttempt]) -> None:
        """Log detailed information about final failure"""
        logger.error(f"Final failure for {file_path} after {len(attempts)} attempts:")
        
        for i, attempt in enumerate(attempts, 1):
            logger.error(f"Attempt {i}:")
            logger.error(f"  Error Type: {attempt.error.error_type if attempt.error else 'Unknown'}")
            logger.error(f"  Error: {attempt.error.error_message if attempt.error else 'No error info'}")
            logger.error(f"  Timestamp: {attempt.timestamp}")
            
        logger.error("Manual intervention required for this file")